package partition;

import jdd.bdd.BDDIO;
import network.Device;
import network.Port;
import verifier.util.BDDEngine;
import verifier.util.PPM;
import verifier.JiffyVerifier;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.HashSet;
import java.util.LinkedList;

public class LocalTask {
    private Device ingress;
    private Port egress;
    private HashSet<Integer> hs;
    // the out put hs of egress in union of all ecs into one bdd, since OBS will not split it
    private int ouths = 0;
    private byte[] hsbytes;
    private PPM ppm;
    private BDDEngine bdd;

    public LocalTask(Device ingress, Port egress) {
        this.ingress = ingress;
        this.egress = egress;
        this.hs = new HashSet<>();
    }

    public void run(JiffyVerifier verifier) {
//        PPM ppm = new PPM(verifier.portsToPredicate, verifier.bddEngine);
        // NOTE: the bddEngine is created after partition.run() called
        this.bdd = this.egress.getDevice().getPartition().getVerifier().bddEngine;
        this.ppm = ppm;
        Device device = this.ingress;
        if (ppm.getPortToPreds().get(this.egress) == null) { // if no hs goes to the egress
            return;
        }
        HashSet<Integer> pset = new HashSet<>(ppm.getPortToPreds().get(this.egress));
        LinkedList<Device> history = new LinkedList<>();
        this.traverse(device, pset, history);
        this.ouths = this.hs.stream().reduce(0, (x, y) -> this.bdd.or(x, y));
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        try {
            BDDIO.save(this.bdd.getBdd(), this.ouths, bos);
            this.hsbytes = bos.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        }
//        System.out.println(this.hs.size());
    }

    /**
     * A traverse is a DFS function. During the traverse, it (1) detects loop; (2) detect blackhole.
     * For a device s, it enumerates all possible edge and traversing them with subsets of pset
     * taking intersection with the set of predicates labeled on each edge.
     *
     * @param s the current location (as device)
     * @param pset the set of predicates reaching s
     * @param history the set of devices traversed before.
     */
    private void traverse(Device s, HashSet<Integer> pset, LinkedList<Device> history) {
        if (pset.isEmpty()) {
            return;
        }
        if (history.contains(s)) { // loop
            // Logger.addVerificationReports("DFS-based Verifier" , "Loop", s.getName());
            return;
        }
        for (Port port : s.getPorts()) {
            if (port.getName().equals("default")) { // black-hole
                continue;
            }
            if (ppm.getPortToPreds().get(port) == null) {
                continue;
            }
            HashSet<Integer> npset = (HashSet<Integer>) pset.clone();
            npset.retainAll(ppm.getPortToPreds().get(port));
            LinkedList<Device> nhistory = (LinkedList<Device>) history.clone();
            nhistory.add(s);

            if (port == this.egress) {
                this.hs.addAll(npset);
                break;
            }
            if (port.getPeer() != null && port.getPeer().getPartition() == this.ingress.getPartition()) {
//            this.traverse(port.getPeer().getDevice(), pset.stream().filter(ppm.portToPreds.get(port)::contains).collect(Collectors.toSet()), nhistory);
                this.traverse(port.getPeer().getDevice(), npset, nhistory);
            }
        }
    }

    public Device getIngress() {
        return ingress;
    }

    public Port getEgress() {
        return egress;
    }

    public HashSet<Integer> getHs() {
        return hs;
    }

    public int getOuths() {
        return ouths;
    }

    public byte[] getHsbytes() {
        return hsbytes;
    }
}
